Skip to content

[Change]Improve ringing lifecycle#1078

Merged
ipavlidakis merged 2 commits intodevelopfrom
iliaspavlidakis/ios-1468-enhancementwhen-moving-to-background-during-outgoing-call
Mar 12, 2026
Merged

[Change]Improve ringing lifecycle#1078
ipavlidakis merged 2 commits intodevelopfrom
iliaspavlidakis/ios-1468-enhancementwhen-moving-to-background-during-outgoing-call

Conversation

@ipavlidakis
Copy link
Contributor

@ipavlidakis ipavlidakis commented Mar 5, 2026

🔗 Issue Links

Resolves https://linear.app/stream/issue/IOS-1468/enhancementwhen-moving-to-background-during-outgoing-call-ringing-end

🎯 Goal

Harden the ringing and call lifecycle flows.

🛠 Implementation

Outgoing ringing is now observed explicitly so a matching ringing call is ended if the app transitions to the background before the ring flow completes. The call state machine and related cleanup paths were updated so ringingCall and activeCall remain consistent across join, reject, timeout, and end scenarios.

🧪 Manual Testing Notes

  • Start an outgoing ringing call and move the app to the background before it is answered; verify the call is ended.

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change follows zero ⚠️ policy (required)
  • This change should receive manual QA
  • Changelog is updated with client-facing changes
  • New code is covered by unit tests
  • Comparison screenshots added for visual changes
  • Affected documentation updated (tutorial, CMS)

Summary by CodeRabbit

  • New Features
    • The SDK will automatically end an outgoing call if the app moves to the background while the call is ringing.
  • Documentation
    • Exposes the current ringing call state for observation and documents its lifecycle.
  • Tests
    • Adds unit tests verifying background behavior and handler invocation for ringing calls.

@ipavlidakis ipavlidakis self-assigned this Mar 5, 2026
@ipavlidakis ipavlidakis requested a review from a team as a code owner March 5, 2026 19:04
@ipavlidakis ipavlidakis added the enhancement New feature or request label Mar 5, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Adds automatic termination for outgoing ringing calls when the app moves to the background via a new internal OutgoingRingingController that observes StreamVideo.State.ringingCall and application lifecycle, with Call wiring to create and clean up the controller.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added entry documenting SDK will end outgoing ringing calls when app moves to background.
New Controller
Sources/StreamVideo/Controllers/OutgoingRingingController.swift
New internal OutgoingRingingController that subscribes to StreamVideo.State.ringingCall and application state; activates an app-state observer when the ringing call CID matches and triggers the provided async handler to end the call on background.
State
Sources/StreamVideo/StreamVideo.swift
Added @Published public internal(set) var ringingCall: Call? to StreamVideo.State to expose the currently ringing call (cleared on join/reject/end/promotion).
Call Integration
Sources/StreamVideo/Call.swift
Introduced outgoingRingingController property, configureOutgoingRingingController() helper, creation when ring operations occur, and cleanup/reset during leave/termination to avoid dangling controller references.
Tests
StreamVideoTests/Controllers/OutgoingRingingController_Tests.swift
New unit tests verifying handler is called when ringing call matches and app enters background, and not called when ringing call does not match.

Sequence Diagram

sequenceDiagram
    actor User
    participant ORingCtrl as OutgoingRingingController
    participant StreamVideo as StreamVideo.State
    participant AppAdapter as ApplicationStateAdapter
    participant Call as Call

    User->>Call: start/create outgoing call (ring = true)
    Call->>ORingCtrl: configureOutgoingRingingController(callCID, handler)
    ORingCtrl->>StreamVideo: subscribe to ringingCall updates
    StreamVideo-->>ORingCtrl: ringingCall set/updated
    alt ringingCall.cid == callCID
        ORingCtrl->>AppAdapter: activate app-state observation
        AppAdapter-->>ORingCtrl: emits background state
        ORingCtrl->>ORingCtrl: log warning
        ORingCtrl->>Call: invoke endCall handler (async)
        Call->>Call: terminate outgoing call
        ORingCtrl->>AppAdapter: deactivate observation
    else ringingCall.cid != callCID
        ORingCtrl->>AppAdapter: ensure observation deactivated
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A little hop, a tiny ring,
When background comes, I end the thing,
I watch the state, I mind the call,
I tidy up before the fall,
A rabbit's fix—swift, calm, and small. 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[Change]Improve ringing lifecycle' directly relates to the main changeset, which introduces lifecycle management for outgoing ringing calls. It accurately captures the primary enhancement: improving how ringing is managed when the app moves to the background.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch iliaspavlidakis/ios-1468-enhancementwhen-moving-to-background-during-outgoing-call

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/StreamVideo/Call.swift (1)

389-401: ⚠️ Potential issue | 🟠 Major

Wire ring(request:) into the outgoing-ringing lifecycle.

ring(request:) triggers outgoing ringing but does not call configureOutgoingRingingController() or set streamVideo.state.ringingCall, so the background auto-end flow is skipped for this API path.

🔧 Proposed fix
 `@discardableResult`
 public func ring(request: RingCallRequest) async throws -> RingCallResponse {
     let response = try await coordinatorClient.ringCall(
         type: callType,
         id: callId,
         ringCallRequest: request
     )
+
+    configureOutgoingRingingController()
+    Task(disposableBag: disposableBag) { `@MainActor` [weak self] in
+        self?.streamVideo.state.ringingCall = self
+    }
+
     return response
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/StreamVideo/Call.swift` around lines 389 - 401, ring(request:)
currently sends the coordinator ring but doesn't start the outgoing-ringing
lifecycle; after the successful coordinatorClient.ringCall(...) returns, set the
ringing call state (e.g., streamVideo.state.ringingCall = callId or an
appropriate RingCallResponse value) and invoke
configureOutgoingRingingController() so the background auto-end flow is
installed before returning; make these calls inside the public func
ring(request: RingCallRequest) after obtaining response and before return, using
the existing configureOutgoingRingingController() method and the
streamVideo.state.ringingCall property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 10: The changelog entry contains a typo: replace the misspelled word
"outgoin" with "outgoing" in the line "- The SDK will now end an outgoin call if
the app moves to background while ringing.
[`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)" so it reads
"...end an outgoing call..."; update the CHANGELOG.md entry accordingly.

In `@Sources/StreamVideo/Call.swift`:
- Line 1570: The outgoingRingingController is only reset in performLeave,
leaving observers alive when a call is rejected or hits terminal paths; update
the reject and terminal cleanup paths to also nil out outgoingRingingController.
Specifically, in the rejecting-stage code that clears ringingCall and removes
the call from cache (refer to Call+RejectingStage.swift where ringingCall is
cleared) and in any terminal/reject handlers, add logic to set
Call.outgoingRingingController = nil (or call the same cleanup helper used by
performLeave) so the controller and its observers are always removed regardless
of how the call ends.

---

Outside diff comments:
In `@Sources/StreamVideo/Call.swift`:
- Around line 389-401: ring(request:) currently sends the coordinator ring but
doesn't start the outgoing-ringing lifecycle; after the successful
coordinatorClient.ringCall(...) returns, set the ringing call state (e.g.,
streamVideo.state.ringingCall = callId or an appropriate RingCallResponse value)
and invoke configureOutgoingRingingController() so the background auto-end flow
is installed before returning; make these calls inside the public func
ring(request: RingCallRequest) after obtaining response and before return, using
the existing configureOutgoingRingingController() method and the
streamVideo.state.ringingCall property.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68421344-095a-4dd6-acf0-075975ce1f47

📥 Commits

Reviewing files that changed from the base of the PR and between ef73a9c and 5d2a411.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • Sources/StreamVideo/Call.swift
  • Sources/StreamVideo/Controllers/OutgoingRingingController.swift
  • Sources/StreamVideo/StreamVideo.swift
  • StreamVideoTests/Controllers/OutgoingRingingController_Tests.swift

### 🔄 Changed
- Propagated publish/unpublish failures from local video and screen-share capture
sessions instead of swallowing them after logging. [#1072](https://github.com/GetStream/stream-video-swift/pull/1072)
- The SDK will now end an outgoin call if the app moves to background while ringing. [#1078](https://github.com/GetStream/stream-video-swift/pull/1078)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the typo in the changelog entry.

Line 10 says outgoin instead of outgoing.

✏️ Proposed fix
-- The SDK will now end an outgoin call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)
+- The SDK will now end an outgoing call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- The SDK will now end an outgoin call if the app moves to background while ringing. [#1078](https://github.com/GetStream/stream-video-swift/pull/1078)
- The SDK will now end an outgoing call if the app moves to background while ringing. [`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)
🧰 Tools
🪛 LanguageTool

[grammar] ~10-~10: Ensure spelling is correct
Context: ...ft/pull/1072) - The SDK will now end an outgoin call if the app moves to background whi...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 10, The changelog entry contains a typo: replace the
misspelled word "outgoin" with "outgoing" in the line "- The SDK will now end an
outgoin call if the app moves to background while ringing.
[`#1078`](https://github.com/GetStream/stream-video-swift/pull/1078)" so it reads
"...end an outgoing call..."; update the CHANGELOG.md entry accordingly.

/// to happen on the call object (e.g. rejoin) will need to fetch a new instance from `StreamVideo`
/// client.
callCache.remove(for: cId)
outgoingRingingController = nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset outgoingRingingController in reject/terminal paths too.

Cleanup currently happens only in performLeave (Line 1570).
In Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift (Lines 94-96), rejection clears ringingCall and removes the call from cache without clearing outgoingRingingController, which can leave stale observers alive while the Call is still retained elsewhere.

🔧 Proposed fix direction
// Sources/StreamVideo/Call.swift
+@MainActor
+internal func clearOutgoingRingingController() {
+    outgoingRingingController = nil
+}

 `@MainActor`
 private func performLeave() {
@@
-    outgoingRingingController = nil
+    clearOutgoingRingingController()
@@
 }
// Sources/StreamVideo/CallStateMachine/Stages/Call+RejectingStage.swift
 if streamVideo.state.ringingCall?.cId == call.cId {
     await Task(disposableBag: disposableBag) { `@MainActor` [weak streamVideo] in
         streamVideo?.state.ringingCall = nil
     }.value
 }
+
+await Task(disposableBag: disposableBag) { `@MainActor` [weak call] in
+    call?.clearOutgoingRingingController()
+}.value

Also applies to: 1788-1793

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/StreamVideo/Call.swift` at line 1570, The outgoingRingingController
is only reset in performLeave, leaving observers alive when a call is rejected
or hits terminal paths; update the reject and terminal cleanup paths to also nil
out outgoingRingingController. Specifically, in the rejecting-stage code that
clears ringingCall and removes the call from cache (refer to
Call+RejectingStage.swift where ringingCall is cleared) and in any
terminal/reject handlers, add logic to set Call.outgoingRingingController = nil
(or call the same cleanup helper used by performLeave) so the controller and its
observers are always removed regardless of how the call ends.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
CHANGELOG.md (1)

10-10: ⚠️ Potential issue | 🟡 Minor

Fix the changelog typo.

Line 10 still says outgoin; it should be outgoing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 10, Fix the typo in CHANGELOG.md by replacing the word
"outgoin" with "outgoing" in the sentence that reads "The SDK will now end an
outgoin call..." so it correctly states "The SDK will now end an outgoing call
if the app moves to background while ringing." Ensure only the single word is
corrected and surrounding punctuation/links remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@CHANGELOG.md`:
- Line 10: Fix the typo in CHANGELOG.md by replacing the word "outgoin" with
"outgoing" in the sentence that reads "The SDK will now end an outgoin call..."
so it correctly states "The SDK will now end an outgoing call if the app moves
to background while ringing." Ensure only the single word is corrected and
surrounding punctuation/links remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9bdb2a35-5ad5-480f-9b13-58266dd8ad70

📥 Commits

Reviewing files that changed from the base of the PR and between 5d2a411 and 5b6f13c.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • Sources/StreamVideo/Call.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Sources/StreamVideo/Call.swift

@github-actions
Copy link

Public Interface

🚀 No changes affecting the public interface.

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title develop branch diff status
StreamVideo 10.05 MB 10.07 MB +17 KB 🟢
StreamVideoSwiftUI 2.45 MB 2.45 MB 0 KB 🟢
StreamVideoUIKit 2.58 MB 2.58 MB 0 KB 🟢
StreamWebRTC 11.09 MB 11.09 MB 0 KB 🟢

@Stream-SDK-Bot
Copy link
Collaborator

StreamVideo XCSize

Object Diff (bytes)
OutgoingRingingController.o +11205
ApplicationLifecycleVideoMuteAdapter.o +4748
Call.o +1090
CallAudioSession.o -209
StreamAppStateAdapter.o -138
StreamCallAudioRecorder+ShouldRecordMiddleware.o -112
Moderation+VideoAdapter.o -100

@sonarqubecloud
Copy link

@ipavlidakis ipavlidakis merged commit d3034e8 into develop Mar 12, 2026
13 checks passed
@ipavlidakis ipavlidakis deleted the iliaspavlidakis/ios-1468-enhancementwhen-moving-to-background-during-outgoing-call branch March 12, 2026 11:28
This was referenced Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants